package cn.wen.rpc.netty.client.idle;

import cn.wen.rpc.netty.client.cache.ChannelCache;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.Timeout;
import io.netty.util.Timer;
import io.netty.util.TimerTask;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

// 用来说明ChannelHandler是否可以在多个channel直接共享使用

/**
 * 心跳是用于服务端和客户端保持有效连接的一种手段，客户端每隔一小段时间发一个心跳包，服务端收到之后不用响应，但是会记下客户端最后一次读的时间。服务器起定时器，定时检测客户端上次读请求的时间超过配置的值，超过就会触发事件，断开连接。
 * 重连机制是连接断开之后，要使用的时候自动重连的机制。
 * 心跳和重连机制，结合起来让服务端和客户端的连接使用更加合理，该断开的断开节省服务端资源，该重连的重连提高可用性。
 * 链路检测狗作用：
 *      当发现连接断了之后，如果想往连接写数据，就自动重新连接上，这个就是重连机制。
 *     如果心跳检测出现问题，有可能是网络的原因，我们不能立马将其从缓存中删除，需要进行重连（重连策略）
 *    重连达到一定次数不成功再进行处理；
 *    重连检测狗，当发现当前的链路不稳定关闭时， 进行12次重连。只有在链路检测失败是再关闭
 *    继承ChannelInboundHandlerAdapter处理类
 *    增加程序的健壮性
 */

// 多个Handler共享
@ChannelHandler.Sharable
@Slf4j
public abstract class ConnectionWatchdog extends ChannelInboundHandlerAdapter
        implements TimerTask,ChannelHandlerHolder, CacheClearHandler {

    private final Bootstrap bootstrap;
    private final InetSocketAddress inetSocketAddress;
    private Timer timer;
    private volatile boolean reconnect = true;
    private int attempts;
    private final CompletableFuture<Channel> completableFuture;
    private final ChannelCache channelCache;
    public ConnectionWatchdog(Bootstrap bootstrap, Timer timer,
                              InetSocketAddress inetSocketAddress,
                              CompletableFuture<Channel> channelCompletableFuture,
                              boolean reconnect, ChannelCache channelCache){
        this.bootstrap = bootstrap;
        this.timer = timer;
        this.inetSocketAddress = inetSocketAddress;
        this.reconnect = reconnect;
        this.completableFuture = channelCompletableFuture;
        this.channelCache = channelCache;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("当前链路已经激活了，重连尝试次数重新置为0");
        super.channelActive(ctx);
        // 代表一个连接 连接上了
        attempts = 0;
        ctx.fireChannelActive();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("链接关闭");

        // 代表未连接，此时应该发生重试策略
        if (reconnect){
            log.info("链接关闭，将进行重连");
            if (attempts < 12){
                attempts++;
                log.info("重连次数:{}",attempts);
            }else{
                // 不继续重连
                reconnect = false;
                // 删除缓存
                clear(inetSocketAddress);
            }
            // 重连时间间隔增加
            int timeout = 2 << attempts;
            timer.newTimeout(this,timeout, TimeUnit.SECONDS);
        }
//        ctx.fireChannelInactive();
    }

    @Override
    public void run(Timeout timeout) throws Exception {
        ChannelFuture future = null;
        synchronized (bootstrap) {
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(handlers());
                }
            });
             future = bootstrap.connect(inetSocketAddress);
        }
        // 定时任务执行的位置
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()){
                    // 重连成功
                    completableFuture.complete(future.channel());
                    channelCache.set(inetSocketAddress,future.channel());
                }else{
                    future.channel().pipeline().fireChannelInactive();
                }
            }
        });
    }
}